/******************************************************************************
  Filename:    JackpotPinataGuts.cpp
  Developer:   Ryan Sauls
  Create date: 8/09/2007
*******************************************************************************
  Description: This is the class implementation for the slot gut of the
               Australian Slot machine called Jackpot Pinatas
*******************************************************************************
        Copyright (c) 2007 - Realtime Gaming.  All rights reserved.
*******************************************************************************/

#include "JackpotPinatasGuts.h"
#include "getrand.h"
#include <functional>

/******************************************************************************
 JackpotPinatasGuts::s_aLineWinSpinResults

 This array is used to quickly get the SpinResult for the number of
 icons.  It is indexed by wheel icon and then count.  So to get the
 SpinResult for 5 Aces, use the following code snippet:

		SpinResult result = s_aLineWinSpinResults[ ICON_BANGLE ][ 5 ];

 In this example result would be set to EVT_FIVE_BANGLE.
******************************************************************************/
JackpotPinatasGuts::SpinResult JackpotPinatasGuts::s_aLineWinSpinResults[][6] = 
{
// Number of icons:		    0			1				        2					    3						4					    5
/* ICON_BANGLE       */	{	EVT_LOSER,	EVT_LOSER,		        EVT_LOSER,			    EVT_THREE_BANGLE,		EVT_FOUR_BANGLE,	    EVT_FIVE_BANGLE},
/* ICON_DONKEY_PINATA*/	{	EVT_LOSER,	EVT_ONE_DONKEY_PINATA,	EVT_TWO_DONKEY_PINATA,	EVT_THREE_DONKEY_PINATA,EVT_FOUR_DONKEY_PINATA,	EVT_FIVE_DONKEY_PINATA},
/* ICON_DOG_PINATA   */	{	EVT_LOSER,	EVT_LOSER,		        EVT_LOSER,			    EVT_LOSER,	            EVT_LOSER,	            EVT_LOSER       	},
/* ICON_BULL_PINATA  */	{	EVT_LOSER,	EVT_LOSER,		        EVT_TWO_BULL_PINATA,    EVT_THREE_BULL_PINATA,	EVT_FOUR_BULL_PINATA,   EVT_FIVE_BULL_PINATA},
/* ICON_WOMAN        */	{	EVT_LOSER,	EVT_LOSER,		        EVT_TWO_WOMAN,		    EVT_THREE_WOMAN,		EVT_FOUR_WOMAN,		    EVT_FIVE_WOMAN		},
/* ICON_TACO         */	{	EVT_LOSER,	EVT_LOSER,		        EVT_LOSER,			    EVT_THREE_TACO,			EVT_FOUR_TACO,		    EVT_FIVE_TACO		},
/* ICON_SOMBRERO     */	{	EVT_LOSER,	EVT_LOSER,		        EVT_LOSER,	            EVT_THREE_SOMBRERO,	    EVT_FOUR_SOMBRERO,	    EVT_FIVE_SOMBRERO	},
/* ICON_ROSE         */	{	EVT_LOSER,	EVT_LOSER,		        EVT_TWO_ROSE,		    EVT_THREE_ROSE,		    EVT_FOUR_ROSE,	        EVT_FIVE_ROSE		},
/* ICON_FIREWORKS    */	{	EVT_LOSER,	EVT_LOSER,		        EVT_TWO_FIREWORKS,		EVT_THREE_FIREWORKS,	EVT_FOUR_FIREWORKS,	    EVT_FIVE_FIREWORKS	},
/* ICON_PRESENT      */	{	EVT_LOSER,	EVT_LOSER,		        EVT_LOSER,		        EVT_THREE_PRESENT,		EVT_FOUR_PRESENT,	    EVT_FIVE_PRESENT	},
/* ICON_MARACAS      */	{	EVT_LOSER,	EVT_LOSER,		        EVT_LOSER,			    EVT_THREE_MARACAS,		EVT_FOUR_MARACAS,	    EVT_FIVE_MARACAS	},
/* ICON_LIME         */	{	EVT_LOSER,	EVT_LOSER,		        EVT_LOSER,		        EVT_THREE_LIME,		    EVT_FOUR_LIME,	        EVT_FIVE_LIME		}
};

/******************************************************************************
 JackpotPinatasGuts::s_aScatterSpinResults

 This array is used to quickly get the SpinResult for the number of
 scatter icons.  It is indexed by icon count only, since it only stores
 "Dog Pinata" results.  So to get the SpinResult for 5 Dog Pinatas,
 use the following code snippet:

		SpinResult result = m_aScatterWinSpinResults[ 5 ];

 In this example result would be set to EVT_FIVE_DOG_PINATA.
******************************************************************************/
JackpotPinatasGuts::SpinResult JackpotPinatasGuts::s_aScatterSpinResults[6] = 
{
	// ICON_DOG_PINATA	   Number of Icons
	EVT_LOSER,				// 0
	EVT_SCATTER_NEAR_MISS,	// 1
	EVT_SCATTER_NEAR_MISS,	// 2
	EVT_THREE_DOG_PINATA,	// 3
	EVT_FOUR_DOG_PINATA,	// 4
	EVT_FIVE_DOG_PINATA 	// 5
};

/******************************************************************************
 @fn    JackpotPinatasGuts::JackpotPinatasGuts

 Constructor for the JackpotPinatasGuts class
******************************************************************************/
JackpotPinatasGuts::JackpotPinatasGuts(void) :
    m_nBonusRoundsIndex (0),
    m_nBonusPrizesIndex (0),
    m_nFreeGamesIndex (0)
{
}

/******************************************************************************
 @fn    JackpotPinatasGuts::~JackpotPinatasGuts

 Destructor for the JackpotPinatasGuts class
******************************************************************************/
JackpotPinatasGuts::~JackpotPinatasGuts(void)
{
}

/******************************************************************************
 @fn    bool8 JackpotPinatasGuts::isValidGuts()

 Returns an indication of whether or not the loaded guts is valid.
******************************************************************************/
bool8 JackpotPinatasGuts::isValidGuts()
{
    return VALID_GUTS_START <= getGutsId() && getGutsId() <= VALID_GUTS_END;
}


/******************************************************************************
 @fn    void JackpotPinatasGuts::preSpinClassifyResults

 Framework override that executes before the spin results are classified.
 Overridden to clear out any jackpot-carrying spin lines.
******************************************************************************/
void JackpotPinatasGuts::preSpinClassifyResults( SlotSpin* spin )
{
    baseclass::preSpinClassifyResults( spin );

    resetJackpotSpinLine();
}

/******************************************************************************
 @fn    void JackpotPinatasGuts::classifyStops

 @param spinLine    -   The SpinLine* to evaluate
 @param stops       -   The current values for the wheel stops
 @param bet         -   The current bet
 @param line        -   Which payline is being evaluated
 @param rtnresult   -   The resulting payout event
 @multiplier        -   The multiplier, if any for the payout

******************************************************************************/
void JackpotPinatasGuts::classifyStops( SpinLine* spinline, uint8* stops, uint8 bet, uint8 line,
		uint8& rtnresult, uint8& multiplier )
{
    // Track any Donkey Pinata line wins separately, because we'll use the substitution if it pays more
    SpinResult srDonkeyPinataLineWinResult = getDonkeyPinataWinResult( stops );

    // Now, what's the payout if we use any Donkey Pinata substitutes?
    //  What's the icon on the first reel?
    const SlotIcon *testIcon = getIcon( getGameModeId(), 0, stops[0] );
    ASSERT( testIcon );

    // A variable to track if we actually use the substitution icon
    bool bSubstitution (false);

    // Get the line win, including any substitutions that are made
    SpinResult srLineWinResultWithSubstitution = 
        getLineWinResultWithPossibleSubstitution( stops, line, testIcon->iconid, bSubstitution );

    // If this is the progressive winner
    if ( srLineWinResultWithSubstitution == EVT_FIVE_BULL_PINATA )
    {
        // NEVER substitute them into a progressive win
        ASSERT( !bSubstitution )

        // This is a progressive winning result.
        setJackpotSpinLine( spinline->lineid );

        // And pass it back.
        rtnresult = srLineWinResultWithSubstitution;

    }
    // Else, Use the larger of the two payouts
    else if (isSubstituteIconLineWinGreater(srDonkeyPinataLineWinResult, srLineWinResultWithSubstitution) )
    {
        // The Donkey Pinatas pay more as a line win.
        rtnresult = srDonkeyPinataLineWinResult;
    }
    else
    {
        // Use the substitution-based line win, since it pays more.
        rtnresult = srLineWinResultWithSubstitution;

        // Attach the note, since we used the substitutions.
        if ( bSubstitution && srLineWinResultWithSubstitution != EVT_LOSER )
        {
            // Add the multiplier
            spinline->addNote( NOTE_SUBSTITUTION );
        }
    }

    // Now, see if we have the free game multiplier, which does NOT apply to
    // Bull Pinatas
    if ( getGameModeId() == MODE_FREE_SPIN && EVT_LOSER != rtnresult && !isBullPinataWin(rtnresult))
    {
        multiplier = MULTIPLIER_FREE_SPIN;
        spinline->addNote( NOTE_FREE_SPIN );
    }       
}


/****************************************************************************
 @fn 	 JackpotPinatasGuts::postSpinClassifyResults( SlotSpin* spin )

 @param   spin       Pointer to the spin with stop data.

 This method is overridden to see if we can complete the game.
****************************************************************************/
void JackpotPinatasGuts::postSpinClassifyResults( SlotSpin* spin )
{
	// If we have not reached the feature cap yet and we have feature rounds...
    if ( !getMaxPayoutAwarded() && getPinatasPicksRemaining() )
	{
		// The game can't complete until the bonus game is done.
		setGameCanComplete( false );
		setBonusGameState( BONUS_GAME_ACTIVE );
	}
}

/****************************************************************************
 @fn 	 JackpotPinatasGuts::classifyScatters( SlotSpin* spin )

 @param   spin       Pointer to the spin with stop data.

 This is an override of the framework method.  
 It determines if the current spin merits free spins or scatter payments.
****************************************************************************/
void JackpotPinatasGuts::classifyScatters( SlotSpin *spin )
{
    // This game has a cross-casino progressive, instead of the local one.
    // See if the five bull line win triggered it, so we can add the note
    int32 jackpotSpinLine = getJackpotSpinLine();
    if ( jackpotSpinLine != -1 )
    {
        spin->getSpinLineByLineId( jackpotSpinLine )->jackpot = true;
		// [AO 2008-01-02] Don't trigger a malfunction on a jackpot win
    }

    // Holders for the count and position of scatter icons
    int32 nScatterIconCount = 0;
    int32 nScatterIconPositions = 0;

    SpinResult scatterResult = getScatterResults( spin, nScatterIconCount, nScatterIconPositions );

    if ( scatterResult != EVT_LOSER )
    {
        int32 payout = getPayout(scatterResult)->payoutAmount;

		// Scatters payout based on the total bet, so factor in the number of bet lines.
		payout *= spin->getSpinLineCount();

        int32 multiplier = 1;

		// During free spin feature there are multipliers applied.
		if ( getGameModeId() == MODE_FREE_SPIN )
		{
			multiplier *= MULTIPLIER_FREE_SPIN;
		}       
        
		// Add the payout to the total win for this spin.
		increaseTotalWin( payout * multiplier );

        spin->addScatter( nScatterIconPositions, payout  * getBetPerLine(), 
            multiplier, false, scatterResult, 0 );
    }

    addFeatureTriggerScatter( spin, nScatterIconCount, nScatterIconPositions );
}

/****************************************************************************
 @fn 	 JackpotPinatasGuts::getScatterResults()   

 @param   spin						The current spin object.
 @param   nNumberScatterIcons		The number of scatter icons that we find.
 @param   nScatterPositions		    The bit mask for the scatter positions that we find.

 @return	 SpinResult			A payout from the SpinResult enumeration.

 This helper method is used to evaluate the spin for scatters.
****************************************************************************/
JackpotPinatasGuts::SpinResult JackpotPinatasGuts::getScatterResults( SlotSpin *spin, int32& nNumberScatterIcons, 
                                                 int32& nScatterPositions ) const
{	
    // Initialize the counter and scatter positions.
	nNumberScatterIcons = 0;
	nScatterPositions = 0;

	// Loop through each reel.
  	for( int reel = 0; reel < getReelCount(); reel++ )
	{
		// The position bit for the current icon in case it needs to be stored in a mask.
		uint8 position = reel;

		// Loop through each row in the wheel house.
		for( int row = 0; row < 3; row++ )
		{
			// Get the current stop.
			uint8 stop = relativeWheelPosition( getGameModeId(), reel,
				spin->getStops()[ reel ], row );

			// Get the icon for the current stop.
			uint8 currentIcon = getIcon( getGameModeId(), reel, stop )->iconid;
			
			// If the icon is the one we are looking for...
			if ( currentIcon == ICON_DOG_PINATA )
			{
				// then increase the icon count
				nNumberScatterIcons++;

				// Set the bit in the position mask for this stop.
				nScatterPositions |= 1 << position;
			}

			// Move to the next bit in the position mask.
            position += getReelCount();
		}
	}

	// Use the array to look up the spin result for nNumberScatterIcons.
    return lookupScatterSpinResult( nNumberScatterIcons );
}

/***************************************************************************
 @fn 	 JackpotPinatasGuts::addFeatureTriggerScatter()

 @param   spin						The current spin object.
 @param   nNumberScatterIcons		The number of scatter icons that we find.
 @param   nScatterPositions		The bit mask for the scatter positions that we find.

 This function determines if the current spin triggered the bonus game
 and adds the appropriate scatter.
***************************************************************************/
void JackpotPinatasGuts::addFeatureTriggerScatter( SlotSpin* spin, int32 nNumScatterIcons, int32 nScatterPositions )
{
    // Initialize the pinata feature
    if ( nNumScatterIcons >= 3 )
    {   
        // Then it really is a feature scatter     
		// If we have not reached the maximum payout already...
		if ( getTotalWin() < getFeatureGameCap() )
		{
			// then the game can't complete until the bonus game is done.
			setGameCanComplete( false );
			setBonusGameState( BONUS_GAME_ACTIVE );
            beginNewFeature();
		}
        
        // Add the data to the spin.
        spin->addScatter( nScatterPositions, 0, 0, false, EVT_PINATA_FEATURE, 0 );
    }
    else if ( nNumScatterIcons == 2 )
    {
        spin->addScatter( nScatterPositions, 0, 0, false, EVT_FEATURE_NEAR_MISS, 0 );
    }
}

/***************************************************************************
 @fn 	 JackpotPinatasGuts::getGameModeMultiplierAndNoteId( uint8& nGameModeMultiplier, uint8& nNoteId )

 @param   nGameModeMultiplier	The multiplier for the game mode.
 @param   nNoteId				The note ID for the game mode.

 This helper method is used to get the game mode multiplier and note ID.
***************************************************************************/
void JackpotPinatasGuts::getGameModeMultiplierAndNoteId( uint8& nGameModeMultiplier, uint8& nNoteId )
{
    // We can't do our multiplying and noting here, because these values
    // apply to ALL line wins.  There is no free game multiplier for Bull Pinata
    // wins, so we do the multiplying and noting in the classifyStops function instead.
    // So, just defer to the base class.
    baseclass::getGameModeMultiplierAndNoteId( nGameModeMultiplier, nNoteId );
}

/***************************************************************************
 @fn 	 JackpotPinatasGuts::hasSavedGames()

 This helper method is used to tell the framework to use saved games.
***************************************************************************/
bool8 JackpotPinatasGuts::hasSavedGames()
{
    return true;
}

/***************************************************************************
    * @brief Get the progressive contribution for the current spin.
    * @returns true to allow the us to calculate the
    * contribution ourselves.    
***************************************************************************/
bool8 JackpotPinatasGuts::getProgressiveContribution(int32 nBetPerLine, int32 nLineCount, int32& nContribution, int32& nContributionFractionalCents)
{
    return baseclass::getProgressiveContribution( nBetPerLine, nLineCount, nContribution, nContributionFractionalCents );
}


/******************************************************************************
 @fn    bool8 JackpotPinatasGuts::processClientData
 
 Framework override that handles the subgame data sent by the client.
 Implements the feature game.

 @return    true if a response should be sent back to the client.
******************************************************************************/
bool8 JackpotPinatasGuts::processClientData( uint8 bonusGameType, int32 bonusDataCount, int32 *bonusData )
{
    bool8 bReply = false;

    // Are we in the state where we expect a client data message?
    if ( getBonusGameState() == BONUS_GAME_ACTIVE && bonusGameType == BONUS_GAME_PINATA_FEATURE)
    {
        // Are there really picks remaining?
        int32 picksLeft = getPinatasPicksRemaining();

        if ( picksLeft )
        {  
            // Reset this data if this is the first pick of a round.
            if ( picksLeft % 2 == 0 )
            {
                setPinataSelectedMask( 0 );
                // And that we gave away any bonus rounds
                resetThisRoundBonusRoundFlag();
            }

            // We only want one piece of data - which of the 3 pinatas did they pick?
            if ( bonusDataCount == 1 )
            {
                // Extract & sanitize it
                int32 pinataPick = *bonusData;

                // Is it in range?
                if ( 0 <= pinataPick && pinataPick < NUM_PINATA_CHOICES && !isPinataSelected( pinataPick ) )
                {
                    updatePinataSelectedMask( pinataPick );

                    // Tell the client which pinata this information is for
                    m_subGame->update( SGDIDX_CURRENT_PINATA_PICK, pinataPick );

                    // Tell the client about their win
                    int32 prizeMultiplier = getPinataPickPrize();

                    m_subGame->update( SGDIDX_CURRENT_PINATA_PRIZE, prizeMultiplier );

                    // How much did they really win?                    
                    int32 baseAmount = getBetPerLine() * getNumLinesBet();

                    // 2x awarded during free spins
                    if ( getGameModeId() == MODE_FREE_SPIN )
                    {
                        prizeMultiplier *= MULTIPLIER_FREE_SPIN;
                    }

                    int32 totalAmount = baseAmount * prizeMultiplier;

                    // And record the outcome in the database
                    addBonusGame(0, postIncrementBonusGameResultIndex(), baseAmount,
                        prizeMultiplier, totalAmount, 0);

                    // See if this pick needs to get a rose
                    if( pinataHasRose())
                    {
                        m_subGame->update( SGDIDX_CURRENT_PICK_HAS_ROSE, true );

                        // Ok, we've given away a bonus round in this round.
                        setThisRoundBonusRoundFlag();

                        // We've told the player about one of the bonus rounds,
                        // so, decrement the number of bouns rounds left to give away
                        adjustBonusRoundsRemaining( DECREMENT );
                    }
                    else
                    {
                        m_subGame->update( SGDIDX_CURRENT_PICK_HAS_ROSE, false );
                    }

                    // decrement the number of picks last
                    decrementPicksThisRound();
                    picksLeft = adjustPinataPicksRemaining( DECREMENT );

                    // Now, if there are now no more picks remaining, wrap it all up
                    if ( !picksLeft )
                    {
                        // Bonus game is done.  Award free spins now.
                        awardFreeGames();
                    }
                    

                    // If we reached the maximum payout already...
                    if ( picksLeft && getMaxPayoutAwarded() )
                    {
                        // then clear out the remaining bonus rounds.
                        adjustPinataPicksRemaining( DECREMENT * getPinatasPicksRemaining() );
                        decrementPicksThisRound();
                        setBonusGameState(BONUS_GAME_INACTIVE);
                        setGameCanComplete(true);

                    }
                    // Reset to a 'new round' state if there is an even number of picks remaining
                    else if ( picksLeft % 2 == 0 )
                    {
                        // Move on to the next round
                        incrementCurrentRound();
                        // Forget the pinatas that were chosen in the last round.
                        resetPicksThisRound();
                        setPinataSelectedMask( 0 );
                    }

                    // And we need to send a reply
                    bReply = true;
                }
            }
        }
    }
    return bReply;
}

/**************************************************************************
 @fn        JackpotPinatas::SpinResult GreenLightGuts::isSubstituteIconLineWinGreater( SpinResult srSubIconLineWin, 
                                                                                        SpinResult srTestResult) const

 @param     srSubIconLineWin		The spin result for the line win for N substitution icons
 @param     srTestResult			The spin result for the line win when actually using the substitution

 @return    bool                    true if it pays more to use line win for N substitution icons.

 This helper method is used determine if it pays more to use substitute icons as substitutes
 or as a line win in its own right.
**************************************************************************/
bool JackpotPinatasGuts::isSubstituteIconLineWinGreater( SpinResult srSubIconLineWin, SpinResult srTestResult) const
{
    // What's the payout for the line win for the subtitution icon?
    long nSubIconLineWinPayout = getPayout( srSubIconLineWin )->payoutAmount;

    // What's the payout for the test result?
    long nTestResultPayout = getPayout( srTestResult )->payoutAmount;
    
    // Return whether the first payout is higher than the test
    // Use >= to prefere the line win over substitutions
    return nSubIconLineWinPayout >= nTestResultPayout ;
}

/**************************************************************************
 @fn        SpinResult JackpotPinatasGuts::getDonkeyPinataWinResult() const

 @param     stops			The array of stop positions for the line.

 @return    SpinResult		A payout from the SpinResult enumeration.

 This helper method is used to calculate the "Donkey Pinata" line payout,
 because the icon can be used in either a substitution or its own line win.
**************************************************************************/
JackpotPinatasGuts::SpinResult JackpotPinatasGuts::getDonkeyPinataWinResult( uint8* stops ) const
{
	// Count how many "Donkey Pinata" icons we have at the beginning of the line.
	uint8 donkeyPinataCount = 0;

    // We can count up to the reel count
	// If this icon is a Donkey Pinata...
	while ( donkeyPinataCount < getReelCount() && isDonkeyIcon( stops, donkeyPinataCount ) )
    {
        // then count it.
        ++donkeyPinataCount;
    }

	// Use the array to look up the spin result for n Driver icons.
	return lookupLineWinSpinResult( ICON_DONKEY_PINATA, donkeyPinataCount);
}


/**************************************************************************
 @fn        bool8 JackpotPinatasGuts::isDonkeyIcon() const

 @param     pStops			The array of stop positions for the line.
 @param     reel			The reel index to check

 @return    bool8		Whether or not the specified icon is a Donkey Pinata
                        in the current game mode

 This helper method is used to calculate the "Donkey Pinata" line payout,

**************************************************************************/
bool8 JackpotPinatasGuts::isDonkeyIcon( uint8* pStops, int32 reel ) const
{
    return getIcon( getGameModeId(), reel, pStops[reel] )->iconid == ICON_DONKEY_PINATA;
}

/**************************************************************************
 @fn 	 JackpotPinatasGuts::SpinResult JackpotPinatasGuts::getLineWinResultWithPossibleSubstitution()

 @param     stops			The array of stop positions for the line.
 @param     testIcon		The icon we are looking for, typically the first icon on the line.
 @param     bSubstitution	An output parameter that is set if a subsitution is used.

 @return    SpinResult		A payout from the SpinResult enumeration.

 This helper method is used to calculate the line win payout with a possible substitution.
**************************************************************************/
JackpotPinatasGuts::SpinResult JackpotPinatasGuts::getLineWinResultWithPossibleSubstitution(
    uint8* stops, uint8 line, uint8 testIcon, bool& bSubstitution )
{
    // Initialize output parameter
    bSubstitution = false;
    bool8 bDone = false;

    // Track how many of the count icon we have.  We start with one (the testIcon)
    uint8 iconCount = 1;


    // loop through the remaining reels until the streak is over
    // the testIcon parameter is the icon value on reel 0, so start at 1
    do
    {
        // What icon do we have? (Using iconCount to index into stops for convenience)
        const SlotIcon *icon = getIcon( getGameModeId(), iconCount, stops[iconCount] );
        ASSERT( icon );

        // Does our current icon continue the streak started by the testIcon?
        if ( icon->iconid == testIcon )
        {
            // Yes. Count one more icon
            ++iconCount;
        }
        // Is it a Donkey Pinata?
        // Never substitute for the Bull Pinata
        else if ( icon->iconid == ICON_DONKEY_PINATA && canBeSubstituted(testIcon) )
        {
            // We're substituting
            bSubstitution = true;

            // Yes. Count one more icon
            ++iconCount;
        }
        // Otherwise, if testIcon is the Donkey Pinata, we're still doing
        // a substition, since the line started with the substitution icon.
        // Again, never substitute for the Bull Pinata.
        else if ( testIcon == ICON_DONKEY_PINATA && canBeSubstituted(icon->iconid) )
        {
            // We're substituting
            bSubstitution = true;

            // Change the testIcon to be the one we're looking at now,
            // so we can figure out what the win would be while substituting
            // for that icon.
            testIcon = icon->iconid;

            // Yes. Count one more icon
            ++iconCount;
        }
        else
        {
            // No.  We're done.
            bDone = true;
        }
    }
    while( !bDone && iconCount < getReelCount() );

    // If we're still looking at a Donkey Pinata, then it's a Donkey Pinata Line win.
    // So, don't call it a substitution.
    if ( iconCount == 5 && testIcon == ICON_DONKEY_PINATA )
    {
        bSubstitution = false;
    }

    // Give back the SpinResult for this many of the testIcon
    return lookupLineWinSpinResult( testIcon, iconCount );
}


/******************************************************************************
 @fn    JackpotPinatasGuts::resetJackpotSpinLine()

 Resets the subgame data that remembers if a jackpot was won on a given payline.
******************************************************************************/
void JackpotPinatasGuts::resetJackpotSpinLine()
{
    m_subGame->update( SGDIDX_JACKPOT_SPIN_LINE_INDEX_SERVER, -1 );
    m_subGame->setServerOnly( SGDIDX_JACKPOT_SPIN_LINE_INDEX_SERVER, true );
}
    
/******************************************************************************
 @fn    void JackpotPinatasGuts::setJackpotSpinLine(int32 index)

 Sets the subgame data that remembers which spin line won the progressive
 jackpot.

 @param index   -   the index of the payline that won the jackpot
******************************************************************************/
void JackpotPinatasGuts::setJackpotSpinLine(int32 index)
{
    // Only in-range values.
    if ( 0 <= index && index < this->getPaylineCount() )
    {
        m_subGame->update( SGDIDX_JACKPOT_SPIN_LINE_INDEX_SERVER, index );
    }
}
    
/******************************************************************************
 @fn    void JackpotPinatasGuts::getJackpotSpinLine() const

 Gets the subgame data that remembers which spin line won the progressive
 jackpot.

 @return The index of the payline that won the jackpot
******************************************************************************/
int32 JackpotPinatasGuts::getJackpotSpinLine() const
{
    return m_subGame->getData( SGDIDX_JACKPOT_SPIN_LINE_INDEX_SERVER );
}

/******************************************************************************
 @fn    void JackpotPinatasGuts::beginNewFeature()

 Sets the subgame data to begin a new feature game.
******************************************************************************/
void JackpotPinatasGuts::beginNewFeature()
{
    // First pick has an invalid index
    m_subGame->update( SGDIDX_CURRENT_PINATA_PICK, -1 );
    // No prizes yet
    m_subGame->update( SGDIDX_CURRENT_PINATA_PRIZE, 0 );
    // No roses yet
    m_subGame->update( SGDIDX_CURRENT_PICK_HAS_ROSE, false );

    // There are 2 picks left in this round.
    resetPicksThisRound();

    // No bonus rounds yet
    resetThisRoundBonusRoundFlag();

    // Start at round 0
    initializeCurrentRound();

    // Store off the number of rounds remaining
    m_subGame->update( SGDIDX_ROUNDS_REMAINING_SERVER, 0 );
    m_subGame->setServerOnly( SGDIDX_ROUNDS_REMAINING_SERVER, true );

    // Go ahead and figure out, for the server side, how many extra rounds & picks
    // will be awarded.

    // Add the total number of rounds the player will be given.
    int32 totalRounds = getRandomValue( m_nBonusRoundsIndex, m_BonusRoundsToWin, PROB_TABLE_NUMBER_OF_ROUNDS );
    adjustBonusRoundsRemaining( INCREMENT * totalRounds );

    // Keep the picks remaining to ourselves.
    // This is the number of picks this round.
    this->adjustPinataPicksRemaining( INCREMENT * ( 2 * totalRounds ) );
    m_subGame->setServerOnly( SGDIDX_PICKS_REMAINING_SERVER, true );

    // Nothing has been selected yet.
    m_subGame->update( SGDIDX_BONUS_PICK_SELECTED_MASK_SERVER, 0 );
    m_subGame->setServerOnly( SGDIDX_BONUS_PICK_SELECTED_MASK_SERVER, true );

    // No bonus games have been created yet.
    m_subGame->update( SGDIDX_BONUS_GAME_RESULT_INDEX_SERVER, 0 );
    m_subGame->setServerOnly( SGDIDX_BONUS_GAME_RESULT_INDEX_SERVER, true );
}

/******************************************************************************
 @fn    void JackpotPinatasGuts::resetPicksThisRound()

 Resets the number of picks remaining in a round to its initial value.
******************************************************************************/
void JackpotPinatasGuts::resetPicksThisRound()
{
    m_subGame->update( SGDIDX_PICKS_REMAINING_THIS_ROUND, 2 );
}

/******************************************************************************
 @fn    void JackpotPinatasGuts::decrementPicksThisRound()

 Decrements the number of picks remaining in this round.  Called
 after each pick is made.
******************************************************************************/
void JackpotPinatasGuts::decrementPicksThisRound()
{
    m_subGame->update( SGDIDX_PICKS_REMAINING_THIS_ROUND, 
        m_subGame->getData( SGDIDX_PICKS_REMAINING_THIS_ROUND ) - 1 );
}

/******************************************************************************
 @fn    int32 JackpotPinatasGuts::getPinatasPicksRemaining() const

 Gets the number of picks remaining in this bound round.
******************************************************************************/
int32 JackpotPinatasGuts::getPinatasPicksRemaining() const
{
    return m_subGame->getData( SGDIDX_PICKS_REMAINING_SERVER );
}

/******************************************************************************
 @fn    int32 JackpotPinatasGuts::adjustPinataPicksRemaining(int32 changeAmount)

 Adjusts the number of picks remaining in the entire feature game.

 @param changeAmount    -   the amount to adjust by. 
                            Use the INCREMENT and DECREMENT enums where possible
******************************************************************************/
int32 JackpotPinatasGuts::adjustPinataPicksRemaining(int32 changeAmount)
{
    // Get the old value
    int32 value = m_subGame->getData( SGDIDX_PICKS_REMAINING_SERVER );
    
    // Calculate the new value.
    value += changeAmount;
    m_subGame->update( SGDIDX_PICKS_REMAINING_SERVER, value );

    return value;
}

/******************************************************************************
 @fn    int32 JackpotPinatasGuts::getBonusRoundsRemaining() const

 Gets the number of bonus game rounds remaining to be played.
******************************************************************************/
int32 JackpotPinatasGuts::getBonusRoundsRemaining() const
{
    return m_subGame->getData( SGDIDX_ROUNDS_REMAINING_SERVER );
}

/******************************************************************************
 @fn    int32 JackpotPinatasGuts::adjustBonusRoundsRemaining(int32 changeAmount)

 Adjusts the number of bonus rounds remaining to be played.

 @param changeAmount    -   the amount to adjust by. 
                            Use the INCREMENT and DECREMENT enums where possible
******************************************************************************/
int32 JackpotPinatasGuts::adjustBonusRoundsRemaining(int32 changeAmount)
{
    int32 value = m_subGame->getData( SGDIDX_ROUNDS_REMAINING_SERVER );
    value += changeAmount;
    m_subGame->update( SGDIDX_ROUNDS_REMAINING_SERVER, value );
    return value;
}

/******************************************************************************
 @fn    bool8 JackpotPinatasGuts::pinataHasRose() const

 Gets a value indicating if the 'next' pinata has a 
 bonus game 'rose' associated with it.

 The rules are - 1) if there's a bonus round still to be awarded
                      a) and this is the second pick, award it.
                      b) If this is the first pick, award it 'randomly'
******************************************************************************/
bool8 JackpotPinatasGuts::pinataHasRose() const
{
    // We need to see how many rounds remain to be awarded
    // If there are any (remember that the 'first' round has already been awarded)
    // Also, only award one rose per round.
    if ( getBonusRoundsRemaining() > 1 && !getThisRoundBonusRoundFlag())
    {
        // If there's only one pick left in this round, and we haven't already
        // we have to send it back now
        if ( (getPinatasPicksRemaining() % 2) == 1 )
        {
            return true;
        }
        // Else, if this is the first pick, flip a coin to see if we show it this time.
        else
        {
            return flipACoin();
        }
    }
    // Else, no, there isn't one on this pick
    return false;
}

/******************************************************************************
 @fn    int32 JackpotPinatasGuts::getPinataPickPrize()

 Gets a value to award for a pinata pick in the feature game.
******************************************************************************/
int32 JackpotPinatasGuts::getPinataPickPrize()
{
    return getRandomValue( m_nBonusPrizesIndex, m_BonusPrizesToWin, PROB_TABLE_PINATA_PRIZES );
}

/******************************************************************************
 @fn    void JackpotPinatasGuts::awardFreeGames()

 At the end of the feature game, awards prizes as determined by the probability
 tables.
******************************************************************************/
void JackpotPinatasGuts::awardFreeGames()
{
    int32 freeGamesToAward = getRandomValue( m_nFreeGamesIndex, m_FreeGamesToWin, 
        PROB_TABLE_BONUS_ROUND_FREE_GAMES );

    addBonusGame(0, postIncrementBonusGameResultIndex(), 0, 0, 0, freeGamesToAward);

    incrementFreeSpins( freeGamesToAward );
    setGameModeId( MODE_FREE_SPIN );

    setBonusGameState(BONUS_GAME_INACTIVE);
    setGameCanComplete(true);
}

/******************************************************************************
 @fn    void JackpotPinatasGuts::initializeCurrentRound()

 Initializes the current feature game round to 0.
******************************************************************************/
void JackpotPinatasGuts::initializeCurrentRound()
{
    m_subGame->update( SGDIDX_CURRENT_ROUND, 0 );
}

/******************************************************************************
 @fn    void JackpotPinatasGuts::incrementCurrentRound()

 Advances the feature game current round counter.
******************************************************************************/
void JackpotPinatasGuts::incrementCurrentRound()
{
    m_subGame->update( SGDIDX_CURRENT_ROUND, m_subGame->getData( SGDIDX_CURRENT_ROUND ) + 1);
}

/******************************************************************************
 @fn    int32 JackpotPinatasGuts::getThisRoundBonusRoundFlag() const

 Gets a flag telling whether or not there's been a rose awarded in the
 current round.
******************************************************************************/
int32 JackpotPinatasGuts::getThisRoundBonusRoundFlag() const
{
    return m_subGame->getData( SGDIDX_ROSES_THIS_ROUND );
}

/******************************************************************************
 @fn    int32 JackpotPinatasGuts::setThisRoundBonusRoundFlag()

 Adjusts the number of roses left to be given out in the current feature game.

 @param changeAmount    -   The amount to change the count by.  Use the
                            INCREMENT/DECREMENT enums where possible.
******************************************************************************/
void JackpotPinatasGuts::setThisRoundBonusRoundFlag()
{
    m_subGame->update( SGDIDX_ROSES_THIS_ROUND, 1 );
}

/******************************************************************************
 @fn    void JackpotPinatasGuts::resetThisRoundBonusRoundFlag()

 Initializes the flag for bonus round roses this round.
******************************************************************************/
void JackpotPinatasGuts::resetThisRoundBonusRoundFlag()
{
    m_subGame->update( SGDIDX_ROSES_THIS_ROUND, 0 );
}

/******************************************************************************
 @fn    bool8 JackpotPinatasGuts::isPinataSelected(int32 nPinata) const

 Gets a value indicating if the specified pinata has already been selected.

 @param nPinata -   The index of the pinata to test.
******************************************************************************/
bool8 JackpotPinatasGuts::isPinataSelected(int32 nPinata) const
{
    int32 nMask = getPinataSelectedMask();

    if ( nMask & ( 1 << (1 + nPinata) ) )
    {
        return true;
    }

    return false;
}

/******************************************************************************
 @fn    void  JackpotPinatasGuts::updatePinataSelectedMask( int32 nPinata )

 Updates the mask value indicating which pinatas have already been selected
 in a given bonus round.

 @param nPinata -   The index of the pinata to update as selected.
******************************************************************************/
void  JackpotPinatasGuts::updatePinataSelectedMask( int32 nPinata )
{
	ASSERT( 0 <= nPinata && nPinata < NUM_PINATA_CHOICES );

	int32 nMask = 1 << (1 + nPinata) ;
	int32 nSelectedMask = getPinataSelectedMask();
	nSelectedMask |= nMask;
	setPinataSelectedMask( nSelectedMask );
}

/******************************************************************************
 @fn    void  JackpotPinatasGuts::setPinataSelectedMask( int32 nMask )

 Sets the value of the mask indicating which pinatas have already been selected
 in the current feature round.
******************************************************************************/
void  JackpotPinatasGuts::setPinataSelectedMask( int32 nMask )
{
    m_subGame->update( SGDIDX_BONUS_PICK_SELECTED_MASK_SERVER, nMask );
}

/******************************************************************************
 @fn    int32  JackpotPinatasGuts::getPinataSelectedMask() const

 Gets the mask value indicating which pinatas have already been selected in 
 this feature game round.
******************************************************************************/
int32  JackpotPinatasGuts::getPinataSelectedMask() const
{
    return m_subGame->getData( SGDIDX_BONUS_PICK_SELECTED_MASK_SERVER );
}

/******************************************************************************
 @fn 	 int32 OuttaThisWorldGuts::getBonusGameResultIndex()

 This helper method is used to get the subgame data that tracks which
 index to use with addBonusGame
******************************************************************************/
int32 JackpotPinatasGuts::getBonusGameResultIndex() const
{
	// Return the subgame data.
	return m_subGame->getData( SGDIDX_BONUS_GAME_RESULT_INDEX_SERVER );
}

/******************************************************************************
 @fn 	 int32 OuttaThisWorldGuts::postIncrementBonusGameResultIndex()

 This helper method is used to increment the subgame data that tracks
 which index to use with addBonusGame
******************************************************************************/
int32 JackpotPinatasGuts::postIncrementBonusGameResultIndex()
{
	int32 nBonusGameResultIndex = getBonusGameResultIndex();
    m_subGame->update( SGDIDX_BONUS_GAME_RESULT_INDEX_SERVER, nBonusGameResultIndex + 1 );
	return nBonusGameResultIndex;
}

/*****************************************************************************
 @fn    bool JackpotPinatasGuts::flipACoin() const

 Returns a random boolean value.
******************************************************************************/
bool JackpotPinatasGuts::flipACoin() const
{
    static int32 values[2] = {0,1};

    // I want this method to be const, but the helper I'll call is not
    // so, cast it away just for this call.
    JackpotPinatasGuts* THIS = const_cast<JackpotPinatasGuts*>( this );

    return (1 == THIS->getRandomFromArray( values, 2 ));
}

/*****************************************************************************
DEBUG HELPERS FROM HERE DOWN.
****************************************************************************/

/*****************************************************************************
 @fn    void JackpotPinatasGuts::Debug_SetLoserStops( uint8 *pStops )

 Sets the stops to losing reel values.
*****************************************************************************/
void JackpotPinatasGuts::Debug_SetLoserStops( uint8 *pStops )
{
    pStops[0] = 5;
    pStops[1] = 0;
    pStops[2] = 0;
    pStops[3] = 0;
    pStops[4] = 0;
}

/*****************************************************************************
 @fn    void JackpotPinatasGuts::initDebugFunctionMap()

 Initializes the debug system's function map with our class's debug
 specific functions.
*****************************************************************************/
void JackpotPinatasGuts::initDebugFunctionMap()
{
    baseclass::initDebugFunctionMap();

    addDebugStopFunction( EVT_DEBUG_BONUS_ROUND_0X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetBonusRounds, 0 + 1) );
    addDebugStopFunction( EVT_DEBUG_BONUS_ROUND_1X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetBonusRounds, 1 + 1) );
    addDebugStopFunction( EVT_DEBUG_BONUS_ROUND_2X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetBonusRounds, 2 + 1) );
    addDebugStopFunction( EVT_DEBUG_BONUS_ROUND_3X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetBonusRounds, 3 + 1) );
    addDebugStopFunction( EVT_DEBUG_BONUS_ROUND_4X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetBonusRounds, 4 + 1) );
    addDebugStopFunction( EVT_DEBUG_BONUS_ROUND_5X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetBonusRounds, 5 + 1) );
    
    addDebugStopFunction( EVT_DEBUG_BONUS_PRIZE_2X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetNextBonusPrize, 2 ) );
    addDebugStopFunction( EVT_DEBUG_BONUS_PRIZE_3X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetNextBonusPrize, 3 ) );
    addDebugStopFunction( EVT_DEBUG_BONUS_PRIZE_5X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetNextBonusPrize, 5 ) );
    addDebugStopFunction( EVT_DEBUG_BONUS_PRIZE_10X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetNextBonusPrize, 10 ) );
    addDebugStopFunction( EVT_DEBUG_BONUS_PRIZE_25X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetNextBonusPrize, 25 ) );
    addDebugStopFunction( EVT_DEBUG_BONUS_PRIZE_200X,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetNextBonusPrize, 200 ) );

    addDebugStopFunction( EVT_DEBUG_BONUS_FREE_GAMES_5,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetFeatureFreeGamesPrize, 5 ) );
    addDebugStopFunction( EVT_DEBUG_BONUS_FREE_GAMES_10,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetFeatureFreeGamesPrize, 10 ) );
    addDebugStopFunction( EVT_DEBUG_BONUS_FREE_GAMES_15,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetFeatureFreeGamesPrize, 15 ) );
    addDebugStopFunction( EVT_DEBUG_BONUS_FREE_GAMES_25,
        new DebugStopFunc1Arg<JackpotPinatasGuts, int32>( this, &JackpotPinatasGuts::Debug_SetFeatureFreeGamesPrize, 25 ) );
}

/*****************************************************************************
 @fn    void JackpotPinatasGuts::Debug_SetBonusRounds( int32 numBonusRounds, SlotSpin* )

 Debug function - sets the number of bonus rounds to be awarded on the next 
 bonus game.  Creates a FIFO queue of win values.
*****************************************************************************/
void JackpotPinatasGuts::Debug_SetBonusRounds( int32 numBonusRounds, SlotSpin* )
{
    m_BonusRoundsToWin.push_back( numBonusRounds );
}

/*****************************************************************************
 @fn    void JackpotPinatasGuts::Debug_SetNextBonusPrize( int32 nextPrizeAmount, SlotSpin* )

 Debug function - sets the next bonus round pinata prize to be awarded.
 Creates a FIFO queue of win values.
*****************************************************************************/
void JackpotPinatasGuts::Debug_SetNextBonusPrize( int32 nextPrizeAmount, SlotSpin* )
{
    m_BonusPrizesToWin.push_back( nextPrizeAmount );
}

/*****************************************************************************
 @fn    void JackpotPinatasGuts::Debug_SetFeatureFreeGamesPrize(int32 numFreeGames, SlotSpin *)

 Debug function - sets the next number of bonus round free games to be awarded.
 Creates a FIFO queue of win values.
*****************************************************************************/
void JackpotPinatasGuts::Debug_SetFeatureFreeGamesPrize(int32 numFreeGames, SlotSpin *)
{
    m_FreeGamesToWin.push_back( numFreeGames );
}

/*****************************************************************************
 @fn    int32 JackpotPinatasGuts::getRandomValue( int32& index, std::vector<int32>& vec, int32 defaultProbTable ) const

 Gets a value from the specified probability table, overriding it with
 debug values if present.  We depend on the framework to make debug
 functions unavailable to live deployments.
*****************************************************************************/
int32 JackpotPinatasGuts::getRandomValue( int32& index, std::vector<int32>& vec, int32 defaultProbTable ) const
{
    int32 value ( getRandomValueFromProbabilityTable(defaultProbTable) );

    if ( index < (int32)vec.size() )
    {
        value = vec[index];

        if ( index + 1 < (int32)vec.size() )
        {
            ++index;
        }
        else
        {
            index = 0;
        }
    }

    return value;
}